const app = require('j7/app');
const utils = require('j7/utils');
const log = require('j7/log');

const metaClassList = [];
const metaClasses = {};
const web3ConfHash = {};
let web3BcSpiderConf = null;
let configDir = './config/';

function registerMetaClass(fileName, primKey, wrapClass) {
  const metaClass = {
    'fileName' : fileName,
    'primKey' : primKey,
    'wrapClassName': wrapClass,
    'wrapClass': require('./' + wrapClass),
    'rawList' : [],
    'rawHash' : {},
    'wrapList' : [],
    'wrapHash' : {},
  };
  metaClassList.push(metaClass);
  metaClasses[wrapClass] = metaClass;
}

function checkNetData(net) {
  net['Events'].forEach(
    (item) => {
      let found = false;
      net['Contract'].forEach(
        (c) => {
          if (c['name'] == item['contract_name']) {
            found = true;
          }
        }
      );
      if (!found) {
        throw new Error('checkNetData contract found:' + utils.jsonEncode(item));
      }
    }
  );
}

function load() {
  metaClassList.forEach((metaClass) => {
    const json = utils.readJsonFromFile(metaClass['fileName']);
    if (!json) {
      throw new Error('读取配置' + metaClass['fileName'] + '失败');
    }
    if (Array.isArray(json)) {
      metaClass['rawList'] = json;

    } else {
      metaClass['rawList'].push(json);
    }
    let idx = 0;
    metaClass['rawList'].forEach(function (item) {
      const wrapOjb = new metaClass['wrapClass'](item, metaClass);
      const wrapProxy = new Proxy(wrapOjb, wrapOjb._getHandler());
      metaClass['wrapList'].push(wrapProxy);
      if (metaClass['primKey'] == '') {
        metaClass['rawHash'][idx] = item;
        metaClass['wrapHash'][idx] = wrapProxy;
      } else {
        metaClass['rawHash'][item[metaClass['primKey']]] = item;
        metaClass['wrapHash'][item[metaClass['primKey']]] = wrapProxy;
      }
      ++idx;
    });
    //log.debug(utils.jsonEncode(metaClass));
  });
  {
    for (let i = 0; i < 3; ++i) {
      metaClassList.forEach((metaClass) => {
        metaClass['wrapList'].forEach((item) => {
          if (item['_init' + i]) {
            item['_init' + i](exports);
          }
        });
      });
    }
    metaClassList.forEach((metaClass) => {
      metaClass['wrapList'].forEach((item) => {
        item.lock();
      });
    });
  }
}

async function init() {
  configDir = './config/';
  if (utils.isOnlineEnv()) {
    configDir = '../config/';
  } else if (utils.getArgv('env') == 'dev'){
    configDir = './config_dev/';
  }
  console.log(configDir, utils.getArgv('env'));

  let resDir = './res/';
  if (utils.isOnlineEnv()) {
    resDir = '../res/';
  } else if (utils.getArgv('env') == 'dev'){
    resDir = './res_dev/';
  }
  console.log(resDir);

  registerMetaClass(configDir + 'config.json',
                    '',
                    'Config'
                   );
  registerMetaClass(configDir + 'bcevent_mysql.json',
                    '',
                    'BcEventDb'
                   );
  registerMetaClass(configDir + 'bcnft_mysql.json',
                    '',
                    'BcNftDb'
                   );
  registerMetaClass(configDir + 'web3bcspider.cluster.json',
                    '',
                    'Web3BcSpider'
                   );
  load();
  {
    traverseMetaList('BcEventDb', (dbConf, idx) => {
      app.registerDb('BcEventDb' + idx, dbConf);
    });
    traverseMetaList('BcNftDb', (dbConf, idx) => {
      app.registerDb('BcNftDb' + idx, dbConf);
    });
    traverseMetaList('Web3BcSpider', (item, idx) => {
      if (item['instance_id'] == app.getInstanceId()) {
        web3BcSpiderConf = item;
        item['nets'].forEach((net) => {
          const netId = net['net_id'];
          const netDir = configDir + 'nets/' + netId + '/';
          web3ConfHash[netId] = {
            'net_id': netId,
            'netId': netId,
            'Web3': utils.readJsonFromFile(netDir + 'web3.json'),
            'Contract': utils.readJsonFromFile(netDir + 'contract.json'),
            'Events': net['events'],
            'refresh_erc721': net['refresh_erc721'],
            'refresh_erc1155': net['refresh_erc1155'],
            'refresh_erc20': net['refresh_erc20'],
          };
          checkNetData(web3ConfHash[netId]);
        });
      }
    });
  }
}

function getMetaClass(name) {
  return utils.hasKey(metaClasses, name) ? metaClasses[name] : null;
}

function getMetaByKey(name, key) {
  const metaClass = getMetaClass(name);
  return metaClass && key in metaClass['wrapHash'] ? metaClass['wrapHash'][key] : null;
}

function getMetaList(name) {
  const metaClass = getMetaClass(name);
  return metaClass ? metaClass['wrapList'] : null;
}

function callMetaStatic(name, method, ...args) {
  const metaClass = getMetaClass(name);
  return metaClass['wrapClass'][method](...args);
}

function traverseMetaList(name, cb) {
  const metaList = getMetaList(name);
  if (metaList) {
    for (let i = 0; i < metaList.length; ++i) {
      if (!cb(metaList[i], i, metaList.length)) {
        break;
      }
    }
  }
}

function getConf(netId) {
  const keys = Object.keys(web3ConfHash);
  for (let i = 0; i < keys.length; ++i) {
    const conf = web3ConfHash[keys[i]];
    if (conf['net_id'] == netId) {
      return conf;
    }
  }
  return null;
}

function getWeb3Conf(netId) {
  const conf = getConf(netId);
  return conf ? conf['Web3'] : null;
}

function getWeb3BcSpiderConf() {
  return web3BcSpiderConf;
}

function getContractsConf(netId) {
  const conf = getConf(netId);
  return conf ? conf['Contract'] : null;
}

function getNetList() {
  const netList = [];
  const keys = Object.keys(web3ConfHash);
  for (let i = 0; i < keys.length; ++i) {
    netList.push(web3ConfHash[keys[i]]);
  }
  return netList;
}

function getNetDir(netId) {
  const netDir = configDir + 'nets/' + netId + '/';
  return netDir;
}

exports.init = init;

exports.getMetaByKey = getMetaByKey;
exports.traverseMetaList = traverseMetaList;
exports.callMetaStatic = callMetaStatic;

exports.getWeb3Conf = getWeb3Conf;
exports.getWeb3BcSpiderConf = getWeb3BcSpiderConf;
exports.getContractsConf = getContractsConf;
exports.getNetList = getNetList;
exports.getNetDir = getNetDir;
